package com.gome.boot.config.elasticsearch.tools;

import com.gome.boot.common.logger.BaseLogger;
import com.gome.boot.common.util.JacksonUtils;
import com.gome.boot.config.elasticsearch.cluster.ClusterClient;
import com.gome.boot.config.elasticsearch.cluster.IndexConfig;
import com.gome.boot.config.elasticsearch.cluster.IndexTypeConfig;
import com.gome.boot.config.elasticsearch.cluster.MultiClusterClient;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.UnmodifiableIterator;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.util.Assert;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * ES索引工具基础类.
 *
 * @author baoxiufeng
 */
public abstract class IndexTool<T> extends BaseLogger {

    /**
     * 集群与存在索引名称缓存集合
     */
    protected final Map<String, Set<String>> CLUSTER_INDEX_NAME_CACHE = Maps.newHashMap();
    /**
     * ES多集群客户端工具
     */
    protected MultiClusterClient multiClient;
    /**
     * ES索引配置
     */
    protected Map<String, String> indexSettings;
    /**
     * 索引配置信息
     */
    protected IndexConfig indexConfig;
    /**
     * 索引所有类型集合.
     * <br/>key-类型名、value-类型对应实体类型
     */
    protected Map<Class, IndexTypeConfig> indexTypeMap;

    @Autowired
    protected EntityMapper entityMapper;

    /**
     * 获取索引别名.
     *
     * @return 索引别名
     */
    public String alias() {
        return indexConfig.getIndexAlias();
    }

    /**
     * 获取索引类型.
     *
     * @return 索引类型
     */
    public String type(T entity) {
        return indexTypeMap.get(entity.getClass()).getIndexType();
    }

    /**
     * 获取ID字段名.
     *
     * @return ID字段名
     */
    protected String idFieldName(T entity) {
        return indexTypeMap.get(entity.getClass()).getIdFieldName();
    }

    /**
     * 初始化索引创建.
     */
    protected void init() {
        multiClient.registIndexTool(this);
        for (Map.Entry<String, ClusterClient> entry : multiClient.getClientMap().entrySet()) {
            UnmodifiableIterator<IndexMetaData> iterator = entry.getValue().getClient().admin().cluster()
                    .prepareState()
                    .clear()
                    .setMetaData(true)
                    .get()
                    .getState()
                    .getMetaData()
                    .iterator();
            IndexMetaData metaData = null;
            Set<String> INDEX_NAME_CACHE = getIndexNameCache(entry.getKey());
            while (iterator.hasNext()) {
                metaData = iterator.next();
                if (metaData.getAliases().containsKey(indexConfig.getIndexAlias())) {
                    INDEX_NAME_CACHE.add(metaData.getIndex());
                }
            }
            // 如果不存在索引则创建当前年份索引
            if (INDEX_NAME_CACHE.isEmpty()) {
                // 创建默认索引
                createIndex(entry.getKey(), this.getDefaultIndexName());
            }
            logger.info("[IndexTool.init] cluster[{}], indices: {}", entry.getKey(), JacksonUtils.convertObjectToJson(INDEX_NAME_CACHE));
        }
    }

    /**
     * 构造索引请求.
     *
     * @param entity 索引对象信息
     * @return 索引请求对象
     */
    public IndexRequest buildIndexRequest(T entity) {
        Assert.notNull(entity, "Cannot build from 'null' index entity.");
        try {
            beforeBuildIndexRequest(entity);
            return new IndexRequest(index(entity), type(entity), id(entity)).routing(routing(entity)).source(entityMapper.mapToString(entity));
        } catch (IOException e) {
            logger.error("[IndexTool.buildIndexRequest] Build index request failure.", e);
            return null;
        }
    }

    /**
     * 构造删除索引请求.
     *
     * @param entity 索引对象信息
     * @return 索引删除请求对象
     */
    public DeleteRequest buildDeleteRequest(T entity) {
        Assert.notNull(entity, "Cannot build from 'null' index entity.");
        return new DeleteRequest(index(entity), type(entity), id(entity)).routing(routing(entity));
    }

    /**
     * 指定索引不存在时创建索引.
     *
     * @param indexName 索引名称
     * @return 创建结果
     */
    public boolean createIndexIfNotExsited(String indexName) {
        return createIndex(indexName);
    }

    /**
     * 创建索引.
     *
     * @param indexName 索引名称
     * @return 创建结果（true：成功、false：失败）
     */
    public boolean createIndex(String indexName) {
        boolean ok = false;
        for (String clusterName : CLUSTER_INDEX_NAME_CACHE.keySet()) {
            if (createIndex(clusterName, indexName)) {
                ok = true;
            }
        }
        return ok;
    }

    /**
     * 获取指定集群的索引集.
     *
     * @param clusterName 集群名称
     * @return 集群索引名称集
     */
    private Set<String> getIndexNameCache(String clusterName) {
        Set<String> indexNameCache = CLUSTER_INDEX_NAME_CACHE.get(clusterName);
        if (indexNameCache == null) {
            synchronized (clusterName) {
                if (indexNameCache == null) {
                    CLUSTER_INDEX_NAME_CACHE.put(clusterName, indexNameCache = Sets.<String>newConcurrentHashSet());
                }
            }
        }
        return indexNameCache;
    }

    /**
     * 创建指定集群的索引.
     *
     * @param clusterName 集群名称
     * @param indexName 索引名称
     * @return 创建结果（true：成功、false：失败）
     */
    private boolean createIndex(String clusterName, String indexName) {
        Assert.hasText(clusterName, "Cluster name is required, it must not be null, empty, or blank");
        Assert.hasText(indexName, "Index name is required, it must not be null, empty, or blank");
        Set<String> indexNameCache = getIndexNameCache(clusterName);
        if (indexNameCache.contains(indexName)) {
            return true;
        }
        // 判定集群是否存在
        if (checkIndexExisted(clusterName)) {
            Client client = multiClient.getClient(clusterName).getClient();
            // 查询订单索引名称是否存在
            IndicesAdminClient indicesAdminClient = client.admin().indices();
            IndicesExistsResponse response = indicesAdminClient.prepareExists(indexName).get();
            // 订单索引名称不存在时进行添加
            if (response.isExists()) {
                indexNameCache.add(indexName);
                logger.info("[IndexTool.createIndex] index [{}] existed.", indexName);
                return true;
            }
            Map<String, XContentBuilder> mappings = new HashMap<String, XContentBuilder>(indexTypeMap.size());
            try {
                for (Map.Entry<Class, IndexTypeConfig> entry : indexTypeMap.entrySet()) {
                    mappings.put(entry.getValue().getIndexType(), MappingBuilderTool.buildMapping(entry.getKey(), entry.getValue().getIndexType(), entry.getValue().getIdFieldName(), null));
                }
            } catch (IOException e) {
                logger.error("[IndexTool.createIndex] Fetch index mappings from entity failed.", e);
                return false;
            }
            // 执行索引创建
            CreateIndexRequestBuilder builder = indicesAdminClient
                    .prepareCreate(indexName)
                    .setSettings(indexSettings);
            for (Map.Entry<String, XContentBuilder> entry : mappings.entrySet()) {
                builder.addMapping(entry.getKey(), entry.getValue());
            }
            CreateIndexResponse createIndexResponse = builder.addAlias(new Alias(alias())).get();
            if (createIndexResponse.isAcknowledged()) {
                indexNameCache.add(indexName);
                logger.info("[IndexTool.createIndex] Success to create index, name=[{}], alias=[{}].", indexName, alias());
            } else {
                logger.warn("[IndexTool.createIndex] Fail to create index, name=[{}], alias=[{}].", indexName, alias());
            }
            return createIndexResponse.isAcknowledged();
        }
        return false;
    }

    /**
     * 判定指定索引是否存在.
     *
     * @param indexName 索引名称
     * @return 判定结果（true：存在、false：不存在）
     */
    public boolean indexExisted(String indexName) {
        boolean ok = false;
        for (String clusterName : CLUSTER_INDEX_NAME_CACHE.keySet()) {
            if (indexExisted(clusterName, indexName)) {
                ok = true;
            }
        }
        return ok;
    }

    /**
     * 更新索引.
     *
     * @param indexName 索引名称
     * @return 更新结果（true：成功、false：失败）
     */
    public boolean updateIndex(String indexName) {
        boolean ok = false;
        for (String clusterName : CLUSTER_INDEX_NAME_CACHE.keySet()) {
            if (updateIndex(clusterName, indexName)) {
                ok = true;
            }
        }
        return ok;
    }

    /**
     * 删除指定索引.
     *
     * @param indexName 索引名称
     * @return 删除结果（true：删除成功、false：删除失败）
     */
    public boolean deleteIndex(String indexName) {
        boolean ok = false;
        for (String clusterName : CLUSTER_INDEX_NAME_CACHE.keySet()) {
            if (deleteIndex(clusterName, indexName)) {
                ok = true;
            }
        }
        return ok;
    }

    /**
     * 判定指定索引是否存在.
     *
     * @param indexName 索引名称
     * @return 判定结果（true：存在、false：不存在）
     */
    private boolean indexExisted(String clusterName, String indexName) {
        Assert.hasText(indexName, "Index name is required, it must not be empty.");
        // 判定集群是否存在
        if (checkIndexExisted(clusterName)) {
            Client client = multiClient.getClient(clusterName).getClient();
            return client.admin().indices().prepareExists(indexName).get().isExists();
        }
        return false;
    }

    /**
     * 更新指定集群的索引.
     *
     * @param clusterName 集群名称
     * @param indexName 索引名称
     * @return 更新结果（true：成功、false：失败）
     */
    private boolean updateIndex(String clusterName, String indexName) {
        Assert.hasText(indexName, "Index name is required, it must not be empty.");
        // 判定集群是否存在
        if (checkIndexExisted(clusterName)) {
            Client client = multiClient.getClient(clusterName).getClient();
            // 查询订单索引名称是否存在
            IndicesAdminClient indicesAdminClient = client.admin().indices();
            IndicesExistsResponse response = indicesAdminClient.prepareExists(indexName).get();
            // 订单索引名称不存在时执行添加操作
            if (!response.isExists()) {
                return createIndex(clusterName, indexName);
            }
            Map<String, XContentBuilder> mappings = new HashMap<String, XContentBuilder>(indexTypeMap.size());
            try {
                for (Map.Entry<Class, IndexTypeConfig> entry : indexTypeMap.entrySet()) {
                    mappings.put(entry.getValue().getIndexType(), MappingBuilderTool.buildMapping(entry.getKey(), entry.getValue().getIndexType(), entry.getValue().getIdFieldName(), null));
                }
            } catch (IOException e) {
                logger.error("[IndexTool.updateIndex] Fetch index mappings from entity failed.", e);
                return false;
            }
            // 执行索引更新
            PutMappingResponse putMappingResponse = null;
            for (Map.Entry<String, XContentBuilder> entry : mappings.entrySet()) {
                putMappingResponse = indicesAdminClient
                        .preparePutMapping(indexName)
                        .setType(entry.getKey())
                        .setSource(entry.getValue())
                        .get();
                if (putMappingResponse.isAcknowledged()) {
                    logger.info("[IndexTool.updateIndex] Success to update index, name=[{}], alias=[{}].", indexName, alias());
                } else {
                    logger.warn("[IndexTool.updateIndex] Fail to update index, name=[{}], alias=[{}].", indexName, alias());
                }
            }
            return putMappingResponse.isAcknowledged();
        }
        return false;
    }

    /**
     * 删除指定集群的索引.
     *
     * @param clusterName 集群名称
     * @param indexName 索引名称
     * @return 删除结果（true：删除成功、false：删除失败）
     */
    private boolean deleteIndex(String clusterName, String indexName) {
        Assert.hasText(indexName, "Index name is required, it must not be empty.");
        // 判定集群是否存在
        if (checkIndexExisted(clusterName)) {
            Client client = multiClient.getClient(clusterName).getClient();
            boolean ok = client.admin().indices().prepareDelete(indexName).get().isAcknowledged();
            if (ok) {
                CLUSTER_INDEX_NAME_CACHE.get(clusterName).remove(indexName);
            }
            return ok;
        }
        return false;
    }

    /**
     * 判定指定名称的集群是否存在.
     *
     * @param clusterName 集群名称
     * @return 判定结果（true-存在、false-不存在）
     */
    private boolean checkIndexExisted(String clusterName) {
        if (multiClient.isExisted(clusterName)) {
            return true;
        }
        logger.warn("[IndexTool.checkIndexExisted] The cluster[{}] is not existed.", clusterName);
        return false;
    }

    /**
     * 获取默认索引名称.
     * <br/>主要用于系统启动时初始化创建默认索引.
     *
     * @return 索引名称
     */
    public abstract String getDefaultIndexName();

    /**
     * 获取索引文档ID.
     *
     * @param entity 索引对象信息
     * @return 索引文档ID
     */
    protected abstract String id(T entity);

    /**
     * 获取索引路由值.
     *
     * @param entity 索引对象信息
     * @return 索引路由值
     */
    protected abstract String routing(T entity);

    /**
     * 根据索引丢向信息获取索引名称.
     *
     * @param entity 索引对象信息
     * @return 索引名称
     */
    protected abstract String index(T entity);

    /**
     * 构建索引请求的预处理.
     * <br/>默认空实现，如需要子类自行实现.
     *
     * @param entity 索引对象信息
     */
    protected void beforeBuildIndexRequest(T entity) {
    }
}
